#pragma once

class VariableManager;

class IParamObject
{
public:
	virtual ~IParamObject() { }

	virtual void GetParamValue(uint id, void *outData) const = 0;
	virtual void SetParamValue(uint id, const void *inData) = 0;
};

#define GET_PARAM_VAL(varId,varType,val)   case (varId): *(varType*)outData = (val); break;
#define SET_PARAM_VAL(varId,varType,val)   case (varId): (val) = *(const varType*)inData; break;

class ISimpleParamObject : public IParamObject
{
public:
	virtual void GetParamValue(uint id, void *outData) const { const_cast<ISimpleParamObject*>(this)->SyncParamValue(id, outData, false); }
	virtual void SetParamValue(uint id, const void *inData) { SyncParamValue(id, (void*)inData, true); }

	virtual void SyncParamValue(uint id, void *data, bool set) = 0;
};

#define SYNC_PARAM_VAL(varId,varType,val) \
	case (varId): \
		if (set) \
			(val) = *(const varType*)data; \
		else \
			*(varType*)data = (val); \
		break;

class Variable
{
public:
	enum FLAGS
	{
		FLAG_INVISIBLE = 0x01,
		FLAG_READ_ONLY = 0x02,
	};

	static int NameCmp(const string &name, Variable *var) { return name.compare(var->GetName()); }

	Variable(const string &name, uint id, uint flags = 0) : m_Name(name), m_Id(id), m_Flags(flags) { }
	virtual ~Variable() { }
	
	const string & GetName() { return m_Name; }
	uint GetId() { return m_Id; }
	uint GetFlags() { return m_Flags; }
	bool IsInvisible() { return (m_Flags & FLAG_INVISIBLE) != 0; }
	bool IsReadOnly() { return (m_Flags & FLAG_READ_ONLY) != 0; }

	virtual const char * GetTypeName() = 0;
	virtual void LoadBinary(IParamObject &obj, common::Stream &stream) = 0;
	virtual void SaveBinary(const IParamObject &obj, common::Stream &stream) = 0;
	virtual void LoadText(IParamObject &obj, common::Tokenizer &tokenizer) = 0;
	virtual void SaveText(const IParamObject &obj, TokenWriter &tokenizer) = 0;
	virtual void LoadFromString(IParamObject &obj, const string &s);
	virtual void SaveToString(const IParamObject &obj, string &out);
	virtual void ToShortString(const IParamObject &obj, string &out) { SaveToString(obj, out); }

	void Hash(common::Hash_Calc &calc);

protected:
	virtual void ExtraHash(common::Hash_Calc &calc) { }

private:
	string m_Name;
	uint m_Id;
	uint m_Flags;
};

class VariableManager
{
public:
	static void ConfigTokenWriter(TokenWriter &writer);

	VariableManager();
	~VariableManager();

	void AddVar(Variable *var);
	void FinishInit();
	bool IsInitialized() { return m_Initialized; }

	uint GetHash() { assert(m_Initialized); return m_Hash; }

	size_t GetVarCount() { return m_Variables.size(); }
	Variable * GetVar(size_t index) { return m_Variables[index]; }
	Variable * GetVarSortedByName(size_t index) { return m_VariablesByName[index]; }

	void LoadBinary(IParamObject &obj, common::Stream &stream);
	void SaveBinary(const IParamObject &obj, common::Stream &stream);
	void LoadText(IParamObject &obj, common::Tokenizer &tokenizer);
	void SaveText(const IParamObject &obj, TokenWriter &writer);

	void LoadFromString(IParamObject &obj, const string &s);
	void SaveToString(const IParamObject &obj, string &out);

	Variable * FindVarByName(const string &name);

private:
	typedef std::vector<Variable*> VarVectorType;

	VarVectorType m_Variables; ///< Owner of these objects.
	VarVectorType m_VariablesByName;
	bool m_Initialized;
	uint m_Hash;

	void Hash();
	void SkipValue(common::Tokenizer &tokenizer);
};

template <typename T>
class VariableT : public Variable
{
public:
	VariableT(const string &name, uint id, uint flags = 0) : Variable(name, id, flags) { }

	virtual const char * GetTypeName()
	{
		return typeid(T).name();
	}

	virtual void GetValue(const IParamObject &obj, T &outValue) { obj.GetParamValue(GetId(), &outValue); }
	virtual void SetValue(IParamObject &obj, const T &v) { obj.SetParamValue(GetId(), &v); }
};

template <typename T>
class RawVariable : public VariableT<T>
{
public:
	RawVariable(const string &name, uint id, uint flags = 0) : VariableT<T>(name, id, flags) { }

	virtual void LoadBinary(IParamObject &obj, common::Stream &stream)
	{
		char buf[sizeof(T)];
		stream.MustRead(buf, sizeof(T));
		obj.SetParamValue(GetId(), buf);
	}
	
	virtual void SaveBinary(const IParamObject &obj, common::Stream &stream)
	{
		char buf[sizeof(T)];
		obj.GetParamValue(GetId(), buf);
		stream.Write(buf, sizeof(T));
	}
};
